//
//  MidiInterface.h
//  MusicAnalyzer
//
//  Created by jiro on 12/03/23.
//  Copyright 2012 jiro music. All rights reserved.
//

#pragma once

#include "../DeviceSystem.h"

#include "IOKit/IOKitLib.h"
#include "IOKit/IOCFPlugin.h"
#include "IOKit/usb/IOUSBLib.h"
#include "CoreFoundation/CoreFoundation.h"
#include "/usr/include/mach/mach_port.h"		//	why need? miss setting of framework?

#include "../UtilitySystem.h"
#include "../ExceptionSystem.h"
#include "MidiParser.h"

#include <string>
#include <iostream>
#include <map>
#include <queue>

namespace sc
{
	/*	@brief singleton midi interface class
		@remark MidiInterface is define of device independent Midi interface.
			this class is not dynamic abstracted, becouse of execution speed,
	*/
	template< int identifier >
	class MidiInterface : public IObserver< Buffer >
	{
	public:
		enum 
		{
			DeviceBufferByteCount = 256,
			
			Identifier = identifier,
		};

	public:

		static void CreateInstance( const DeviceInfo& info )
		{
			instance = new MidiInterface< identifier >( info );
			bufferSystem.AddObserver( * instance );
			bufferSystem.StartPolling();
		}

		static MidiInterface* GetInstance()
		{
			return instance;
		}

		static void DestroyInstance()
		{
			(*usbInterface)->USBInterfaceClose( usbInterface );
			delete instance;
			instance = NULL;
		}

		static void SetParseOption( MidiParser::ParseOption option )
		{
			parseOption = option;
		}

		static void AddObserver( void* instance, void (*notify)( void*, std::queue< const MidiMessage* > ) )
		{
			observers.insert( 
				std::pair< void*, void (*)( void*, std::queue< const MidiMessage* > ) >( instance, notify ) );
		}

	protected:

	private:

		MidiInterface( const DeviceInfo& infoArg )
		{
			pipeNumber = 1;		//	for default
			memset( deviceBuffer, 0x00, DeviceBufferByteCount );

			//	examine precondiction
			if ( infoArg.GetType() != DeviceInfo::Type_UsbMidiDevice )
			{
				throw DeviceAccessException( "currently sc::MidiInterface support only usb midi device, " 
					+ std::string( __FILE__ ) + ", "
					+ std::string(  __FUNCTION__ ) );
			}

			//	define variables
			const sc::UsbDeviceInfo info = dynamic_cast< const sc::UsbDeviceInfo& >( infoArg );
			mach_port_t masterPort;
			kern_return_t kernelReturn = err_none;
			CFMutableDictionaryRef matchingDict;

			try 
			{
				//	---- pre process to handle device ---- //
				kernelReturn = IOMasterPort( MACH_PORT_NULL, & masterPort );
				if ( err_none != kernelReturn )
				{
					std::stringstream ss;
					ss << "fail to get master port, IOMasterPort returns "
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				if ( 0 == masterPort )
				{
					std::stringstream ss;
					ss << "fail to get master port, masterPort = 0, error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				//	-------------------------------------- //

				//	---- service matching ---- //
				matchingDict = IOServiceMatching( kIOUSBDeviceClassName );
				if ( NULL == matchingDict )
				{
					throw DeviceAccessException( "IOServiceMatching is failed" );
				}
				int vendorId = info.GetVendorId();
				int productId = info.GetProductId();
				CFDictionarySetValue( 
					matchingDict, CFSTR( kUSBVendorID ), CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt16Type, & vendorId ) );
				CFDictionarySetValue(	
					matchingDict, CFSTR( kUSBProductID), CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, & productId ) );

				io_service_t service;
				IOUSBDeviceInterface** deviceInterFace = NULL;
				kernelReturn = IOServiceGetMatchingServices( masterPort,  matchingDict, & service );
				if ( err_none != kernelReturn )
				{
					std::stringstream ss;
					ss << "fail to get matching service, error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				//	-------------------------- //

				//	---- create andopen device interface ---- //
				kernelReturn =  CreateUSBDeviceInterface( IOIteratorNext( service ), deviceInterFace );		//	use 1st one device
				if ( err_none != kernelReturn )
				{
					std::stringstream ss;
					ss << "fail to create USB device interface, error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				
				kernelReturn = ( * deviceInterFace )->USBDeviceOpen( deviceInterFace );
				if ( kernelReturn != kIOReturnSuccess )
				{
					std::stringstream ss;
					ss << "fail to open USB device, error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				//	----------------------------------- //
				
				//	---- create and open interface interface ---- //
				io_iterator_t iter;
				IOUSBFindInterfaceRequest request;
				IOUSBInterfaceInterface** interfaceInterface;

				request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
				request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
				request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
				request.bAlternateSetting = kIOUSBFindInterfaceDontCare;

				kernelReturn = ( * deviceInterFace )->CreateInterfaceIterator( deviceInterFace, & request, & iter );
				if ( kernelReturn != kIOReturnSuccess )
				{
					std::stringstream ss;
					ss << "fail to create interface iterator , error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );			
				}
				while ( service = IOIteratorNext( iter ) )
				{
					//	---- device create & open ---- //
					HRESULT result;
					IOCFPlugInInterface** pluginInterface;
					SInt32 score;
					kernelReturn = IOCreatePlugInInterfaceForService(
						service, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, & pluginInterface, & score );
					if ( kernelReturn != kIOReturnSuccess || pluginInterface == NULL )
					{
						std::stringstream ss;
						ss << "fail to create plugin interface , error 0x"
							<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
						throw DeviceAccessException( ss.str() );			
					}
					
					result = ( * pluginInterface )->QueryInterface(
						pluginInterface, CFUUIDGetUUIDBytes( kIOUSBInterfaceInterfaceID ), (LPVOID*) & interfaceInterface );
					( * pluginInterface )->Release( pluginInterface );
					kernelReturn = IOObjectRelease( service );
					if ( ( result != 0 ) || ( interfaceInterface == NULL ) )
					{
						std::stringstream ss;
						ss << "fail to create plugin interface , error 0x"
							<< std::hex << result << ": " << IOReturnErrorLookUp::GetMeesage( result );
						throw DeviceAccessException( ss.str() );
					}

					kernelReturn = ( *interfaceInterface )->USBInterfaceOpen( interfaceInterface );
					if ( kIOReturnSuccess != kernelReturn )
					{
						continue;
						std::stringstream ss;
						ss << "fail to open usb interface interface failed, ( *interfaceInterface )->USBInterfaceOpen( interfaceInterface ) error 0x"
							<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
						throw DeviceAccessException( ss.str() );
					}
					//	------------------------------ //

					//	---- decide interface interface to use ---- //
					uint8_t endPointCount = 0;
					kernelReturn = (*interfaceInterface)->GetNumEndpoints( interfaceInterface, & endPointCount );
					if ( kIOReturnSuccess != kernelReturn )
					{
						std::stringstream ss;
						ss << "fail to get number of points failed, (*interfaceInterface)->GetNumEndpoints error 0x"
							<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
						throw DeviceAccessException( ss.str() );
					}
					if ( endPointCount == 0 )
					{
						( * interfaceInterface )->Release( interfaceInterface );
						continue;
					}
					else
					{	//	it specify the interface interface
						break;
					}
					//	------------------------------------------- //
				}
				//	--------------------------------------------- //

				if ( NULL == interfaceInterface )
				{
					std::stringstream ss;
					ss << "fail to create IOUSBInterfaceInterface** interfaceInterface, there is no way to recover";
					throw DeviceAccessException( ss.str() );				
				}

				//	---- set callback to data communication pipe ---- //
				CFRunLoopSourceRef runLoop;
				kernelReturn = (*interfaceInterface)->CreateInterfaceAsyncEventSource( interfaceInterface, & runLoop );
				if ( kernelReturn != kIOReturnSuccess )
				{
					(*interfaceInterface)->Release( interfaceInterface );
					std::stringstream ss;
					ss << "fail to create async loop source in pipe, "
							"(*interfaceInterface)->CreateInterfaceAsyncEventSource error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}

				CFRunLoopAddSource( CFRunLoopGetCurrent(), runLoop, kCFRunLoopDefaultMode );
				kernelReturn = ( * interfaceInterface )->ReadPipeAsync(
					interfaceInterface, pipeNumber, deviceBuffer, DeviceBufferByteCount, 
					& MidiInterface< identifier >::DeviceReadCompleted, static_cast<void*>( interfaceInterface ) );
				if ( kernelReturn != kIOReturnSuccess )
				{
					(*interfaceInterface)->Release( interfaceInterface );
					std::stringstream ss;
					ss << "fail to create read async callback, "
							"( * interfaceInterface )->ReadPipeAsync error 0x"
						<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
					throw DeviceAccessException( ss.str() );
				}
				//	------------------------------------------------- //
				
				usbInterface = interfaceInterface;

			}
			catch ( Exception& e )
			{
				if ( 0 != masterPort )
				{
					mach_port_deallocate( mach_task_self(), masterPort );
				}
				usbInterface = NULL;
				throw;
			}

			if ( 0 != masterPort )
			{
				mach_port_deallocate( mach_task_self(), masterPort );
			}
		}

		IOReturn CreateUSBDeviceInterface( const io_service_t service, IOUSBDeviceInterface**& deviceInterface )
		{
			SInt32 score;
			HRESULT result;
			IOCFPlugInInterface** pluginInterface;
			kern_return_t kernelReturn = IOCreatePlugInInterfaceForService( 
				service, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, & pluginInterface, & score );
			IOObjectRelease( service );
			if ( err_none != kernelReturn )
			{
				return kernelReturn;
			}
			if ( 0 == pluginInterface )
			{
				throw kernelReturn;
			}

			result = ( * pluginInterface )->QueryInterface(
				pluginInterface, CFUUIDGetUUIDBytes( kIOUSBDeviceInterfaceID ), (LPVOID*) & deviceInterface );
			if ( 0 != result || 0 == deviceInterface )
			{
				return result;
			}
			return kIOReturnSuccess;
		}

		void Notify( const Buffer* arg )
		{
			const Buffer* buffer = arg;
			MidiParser parser( parseOption );
			std::queue< const MidiMessage* > messages;
			
			parser.Parse( buffer->dataPtr, buffer->byteCount, messages );
			std::map< void*, void (*)( void*, std::queue< const MidiMessage* > ) >::const_iterator
				iter = observers.begin();
			for ( ; iter != observers.end(); iter++ )
			{
				( iter->second )( iter->first, messages );
			}
			
			while( not messages.empty() )
			{
				PoolAllocator< MidiMessage >::ReturnInstance( const_cast< MidiMessage* >( messages.front() ) );
				messages.pop();
			}
		}

		static void DeviceReadCompleted( void* refCon, IOReturn result, void* arg )
		{
			if ( kIOReturnSuccess != result )
			{
				return;
			}
			
			IOUSBInterfaceInterface** interface = reinterpret_cast< IOUSBInterfaceInterface** >( refCon );
			uint64_t byteCount = reinterpret_cast< uint64_t >( arg );
			bufferSystem.WriteData( deviceBuffer, byteCount );

			//	re arm call back
			kern_return_t kernelReturn;
			kernelReturn = ( * interface )->ReadPipeAsync(
				interface, pipeNumber, deviceBuffer, DeviceBufferByteCount,
				& MidiInterface< identifier >::DeviceReadCompleted, static_cast<void*>( interface ) );
			if ( kernelReturn != kIOReturnSuccess )
			{
				std::stringstream ss;
				ss << "fail to re-arm read async callback, "
						"( * interfaceInterface )->ReadPipeAsync error 0x"
					<< std::hex << kernelReturn << ": " << IOReturnErrorLookUp::GetMeesage( kernelReturn );
				throw DeviceAccessException( ss.str() );
			}
		}

	public:

	protected:

	private:

		static MidiInterface* instance;

		static IOUSBInterfaceInterface** usbInterface;
		
		static int pipeNumber;

		static uint8_t deviceBuffer[ DeviceBufferByteCount ];

		static AsyncBufferSystem bufferSystem;

		static MidiParser::ParseOption parseOption;

		/* @brief notification for  Obj-C language
				key:	instance id, 
				value:	notification function pointer	*/
		static std::map< void*, void (*)( void*, std::queue< const MidiMessage* > ) > observers;

	};

	template< int identifier >
	MidiInterface< identifier >* MidiInterface< identifier >::instance = NULL;

	template< int identifier >
	IOUSBInterfaceInterface** MidiInterface< identifier >::usbInterface = NULL;

	template< int identifier >
	int MidiInterface< identifier >::pipeNumber = 1;

	template< int identifier >
	uint8_t MidiInterface< identifier >::deviceBuffer[ DeviceBufferByteCount ];

	template< int identifier >
	AsyncBufferSystem MidiInterface< identifier >::bufferSystem( 256, 16 );

	template< int identifier >
	MidiParser::ParseOption MidiInterface< identifier >::parseOption = MidiParser::Option_One;

	template< int identifier >
	std::map< void*, void (*)( void*, std::queue< const MidiMessage* > ) > MidiInterface< identifier >::observers;

}